<?php
/**
 * PHPUnit 9.5.28
 */
declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use PHPUnit\Framework\TestCase;
use Cephalopod\Server;
use Cephalopod\ServerContext;
use Cephalopod\LogRecorder;

class ProtectedToPublic extends Server
{
    public static function getRemoteWebsiteConnections(): array
    {
        return static::$remoteWebsiteConnections;
    }

    public static function getBrowserConnections(): array
    {
        return static::$browserConnections;
    }

    public static function setRemoteWebsiteConnections(array $dataToSet)
    {
        static::$remoteWebsiteConnections = $dataToSet;
    }

    public static function setBrowserConnections(array $dataToSet)
    {
        static::$browserConnections = $dataToSet;
    }

    public static function pHandleReadEvent(int $id, string &$readBuffer): int
    {
        return static::handleReadEvent($id, $readBuffer);
    }

    public static function pHandleWriteEvent(int $id, string &$writeBuffer)
    {
        static::handleWriteEvent($id, $writeBuffer);
    }

    public static function pHandleOpenEvent(int $id)
    {
        return static::handleOpenEvent($id);
    }

    public static function pHandleCloseEvent(int $id)
    {
        return static::handleCloseEvent($id);
    }
}

class SrcBasicServerTest extends TestCase
{
    public function __construct()
    {
        LogRecorder::setEnableLog(false);
        parent::__construct();
    }

    public function testSetterAndGetter()
    {
        ProtectedToPublic::setRemoteWebsiteConnections([2 => 'a', 3 => 'b']);
        ProtectedToPublic::setBrowserConnections([4 => 'c', 5 => 'd']);
        $this->assertEquals([2 => 'a', 3 => 'b'], ProtectedToPublic::getRemoteWebsiteConnections());
        $this->assertEquals([4 => 'c', 5 => 'd'], ProtectedToPublic::getBrowserConnections());
    }

    public function testUnknowIdReadException()
    {
        $this->expectException(Exception::class);
        $readBuffer = '';
        ProtectedToPublic::pHandleReadEvent(1, $readBuffer);
    }

    public function testRemoteWebsiteReadEventButPeerIdLessThenZero()
    {
        $this->expectException(Exception::class);
        $context = new ServerContext();
        ProtectedToPublic::setRemoteWebsiteConnections([
            30 => $context,
        ]);
        $readBuffer = '';
        ProtectedToPublic::pHandleReadEvent(30, $readBuffer);
    }

    public function testRemoteWebsiteReadEventButPeerIdNotExists()
    {
        $context = new ServerContext();
        $context->peerId = 32;
        ProtectedToPublic::setRemoteWebsiteConnections([
            31 => $context,
        ]);
        $readBuffer = '1';
        $read = ProtectedToPublic::pHandleReadEvent(31, $readBuffer);
        $this->assertEquals(0, $read);
        $this->assertEquals('1', $readBuffer);
    }

    public function testRemoteWebsiteReadEventAndPeerIdExists()
    {
        $contextWebsite = new ServerContext();
        $contextWebsite->peerId = 34;
        $contextBrowser = new ServerContext();

        ProtectedToPublic::setRemoteWebsiteConnections([
            33 => $contextWebsite,
        ]);
        ProtectedToPublic::setBrowserConnections([
            34 => $contextBrowser,
        ]);

        $readBuffer = '1';
        $read = ProtectedToPublic::pHandleReadEvent(33, $readBuffer);
        $this->assertEquals(1, $read);
        $this->assertEquals('1', $contextBrowser->writeBuffer);
    }

    public function testBrowserSendMessageNotComplete()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            39 => $browserContext,
        ]);
        $readBuffer = "CONNECT example.com:443 HTTP/1.1\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(39, $readBuffer);
        $this->assertEquals(0, $read);
    }

    public function testBrowserSendConnectMessage()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            37 => $browserContext,
        ]);
        $readBuffer = "CONNECT example.com:443 HTTP/1.1\r\n\r\nThis is unknow data";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);
        $this->assertEquals(strlen($readBuffer), $read);
        $newId = $browserContext->peerId;
        $this->assertGreaterThan(0, $newId);
        $this->assertEquals('example.com', $browserContext->websiteName);
        $this->assertEquals("HTTP/1.1 200 Connection Established\r\n\r\n", $browserContext->writeBuffer);
        $this->assertTrue($browserContext->isHttps);
        $this->assertFalse($browserContext->unknowProtocol);
        $remoteWebsiteConnections = ProtectedToPublic::getRemoteWebsiteConnections();
        $remoteWebsiteContext = $remoteWebsiteConnections[$newId];

        $this->assertEquals('example.com', $remoteWebsiteContext->websiteName);
        $this->assertTrue($remoteWebsiteContext->isRemoteWebsite);
        $this->assertEquals(37, $remoteWebsiteContext->peerId);
        $this->assertTrue($remoteWebsiteContext->isHttps);
        $this->assertFalse($remoteWebsiteContext->unknowProtocol);
        $this->assertEquals('This is unknow data', $remoteWebsiteContext->writeBuffer);
    }

    /**
     * @depends testBrowserSendConnectMessage
     */
    public function testBrowserSendHttpsMessage()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            37 => $browserContext,
        ]);
        $readBuffer = "CONNECT example.com:443 HTTP/1.1\r\n\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);
        $newId = $browserContext->peerId;

        $remoteWebsiteConnections = ProtectedToPublic::getRemoteWebsiteConnections();
        $remoteWebsiteContext = $remoteWebsiteConnections[$newId];

        $readBuffer = "ABCDEFG";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);
        $this->assertEquals('ABCDEFG', $remoteWebsiteContext->writeBuffer);
    }

    public function testBrowserSendGetMessage()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            37 => $browserContext,
        ]);
        $readBuffer = "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: keep-alive\r\n\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);
        $this->assertEquals(strlen($readBuffer), $read);
        $newId = $browserContext->peerId;
        $this->assertGreaterThan(0, $newId);
        $this->assertEquals('example.com', $browserContext->websiteName);
        $this->assertEquals("", $browserContext->writeBuffer);
        $this->assertTrue($browserContext->isHttp);
        $this->assertFalse($browserContext->unknowProtocol);
        $remoteWebsiteConnections = ProtectedToPublic::getRemoteWebsiteConnections();
        $remoteWebsiteContext = $remoteWebsiteConnections[$newId];

        $this->assertEquals('example.com', $remoteWebsiteContext->websiteName);
        $this->assertTrue($remoteWebsiteContext->isRemoteWebsite);
        $this->assertEquals(37, $remoteWebsiteContext->peerId);
        $this->assertTrue($remoteWebsiteContext->isHttp);
        $this->assertFalse($remoteWebsiteContext->unknowProtocol);
        $this->assertEquals("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n", $remoteWebsiteContext->writeBuffer);
    }

    /**
     * @depends testBrowserSendGetMessage
     */
    public function testBrowserSendGetMessageHaveUnknowData()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            37 => $browserContext,
        ]);
        $readBuffer = "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: keep-alive\r\n\r\n";
        $readBuffer .= "GET http://example.com/test1 HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: keep-alive\r\n\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);
        $newId = $browserContext->peerId;

        $remoteWebsiteConnections = ProtectedToPublic::getRemoteWebsiteConnections();
        $remoteWebsiteContext = $remoteWebsiteConnections[$newId];

        $this->assertEquals(
            "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n"
            . "GET /test1 HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n"
            ,$remoteWebsiteContext->writeBuffer
        );
    }

    /**
     * @depends testBrowserSendGetMessage
     */
    public function testBrowserContinueSendGetMessage()
    {
        $browserContext = new ServerContext();
        $browserContext->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([
            37 => $browserContext,
        ]);
        $readBuffer = "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: keep-alive\r\n\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);

        $readBuffer = "GET http://example.com/test2 HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: keep-alive\r\n\r\n";
        $read = ProtectedToPublic::pHandleReadEvent(37, $readBuffer);

        $newId = $browserContext->peerId;
        $remoteWebsiteConnections = ProtectedToPublic::getRemoteWebsiteConnections();
        $remoteWebsiteContext = $remoteWebsiteConnections[$newId];

        $this->assertEquals(
            "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n"
            . "GET /test2 HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n"
            ,$remoteWebsiteContext->writeBuffer
        );
    }

    public function testUnknowIdWriteException()
    {
        $this->expectException(Exception::class);
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        ProtectedToPublic::setBrowserConnections([]);
        $writeBuffer = '';
        ProtectedToPublic::pHandleWriteEvent(1, $writeBuffer);
    }

    public function testWriteData()
    {
        $buffer = (string)mt_rand(100, PHP_INT_MAX);
        $context = new ServerContext();
        $context->writeBuffer = $buffer;
        ProtectedToPublic::setRemoteWebsiteConnections([
            20 => $context,
        ]);
        $writeBuffer = '';
        ProtectedToPublic::pHandleWriteEvent(20, $writeBuffer);
        ProtectedToPublic::setRemoteWebsiteConnections([]);
        $this->assertEquals($buffer, $writeBuffer);
        $this->assertEquals('', $context->writeBuffer);
    }

    public function testUnknowIdCloseException()
    {
        $this->expectException(Exception::class);
        ProtectedToPublic::pHandleCloseEvent(1);
    }

    public function testCloseConnection()
    {
        $context = new ServerContext();
        ProtectedToPublic::setRemoteWebsiteConnections([
            10 => $context,
        ]);
        ProtectedToPublic::setBrowserConnections([
            11 => $context,
        ]);
        ProtectedToPublic::pHandleCloseEvent(10);
        ProtectedToPublic::pHandleCloseEvent(11);
        $this->assertEmpty(ProtectedToPublic::getRemoteWebsiteConnections());
        $this->assertEmpty(ProtectedToPublic::getBrowserConnections());
    }

    public function testNotUniqueIdCloseException()
    {
        $this->expectException(Exception::class);
        $context = new ServerContext();
        ProtectedToPublic::setRemoteWebsiteConnections([
            21 => $context,
        ]);
        ProtectedToPublic::setBrowserConnections([
            21 => $context,
        ]);
        ProtectedToPublic::pHandleCloseEvent(21);
    }

    public function testRemoteWebsiteOpenEventButConnectionIsBrowser()
    {
        $this->expectException(Exception::class);
        $context = new ServerContext();
        $context->isRemoteWebsite = false;
        $context->isBrowser = true;
        ProtectedToPublic::setRemoteWebsiteConnections([
            10 => $context,
        ]);
        ProtectedToPublic::pHandleOpenEvent(10);
    }

    public function testRemoteWebsiteOpenEvent()
    {
        $this->expectOutputRegex("/example\\.com/");
        LogRecorder::setEnableLog(true);
        $context = new ServerContext();
        $context->isRemoteWebsite = true;
        $context->isBrowser = false;
        $context->websiteName = 'example.com';
        ProtectedToPublic::setRemoteWebsiteConnections([
            10 => $context,
        ]);
        ProtectedToPublic::pHandleOpenEvent(10);
        LogRecorder::setEnableLog(false);
    }

    public function testBrowserOpenEventButConnectionIsExists()
    {
        $this->expectException(Exception::class);
        $context = new ServerContext();
        $context->isRemoteWebsite = false;
        $context->isBrowser = true;
        ProtectedToPublic::setBrowserConnections([
            11 => $context,
        ]);
        ProtectedToPublic::pHandleOpenEvent(11);
    }

    public function testBrowserOpenEvent()
    {
        ProtectedToPublic::setBrowserConnections([]);
        ProtectedToPublic::pHandleOpenEvent(12);
        $contexts = ProtectedToPublic::getBrowserConnections();
        $this->assertArrayHasKey(12, $contexts);
        $this->assertInstanceOf(ServerContext::class, $contexts[12]);
        $this->assertTrue($contexts[12]->isBrowser);
        $this->assertFalse($contexts[12]->isRemoteWebsite);
    }

    public function testRunMaxException()
    {
        $this->expectException(Exception::class);
        Server::run('1.2.4.8', 10000, 1);
    }
}
